使用 Alibaba Druid 进行 SQL 翻译 您所在的位置:网站首页 Druid sql执行 使用 Alibaba Druid 进行 SQL 翻译

使用 Alibaba Druid 进行 SQL 翻译

2023-11-11 15:05| 来源: 网络整理| 查看: 265

原文地址:https://alphahinex.github.io/2022/05/15/use-druid-to-transform-sql/

cover

description: "Alibaba Druid 不仅仅是一个连接池组件" date: 2022.05.15 10:34 categories: - Java - Database tags: [MySQL, H2, Java, Database, Druid] keywords: MySQL, H2, druid, parser, converter, SQL, SQLUtils

Alibaba Druid logo

Alibaba Druid 是阿里云计算平台 DataWorks 团队出品,为监控而生的数据库连接池。

Apache 旗下也有一个 Apache Druid,是一个高性能的实时分析数据库。

本文提到的 Druid,指 Alibaba Druid。

Druid 其实是一个 JDBC 组件库,不仅包含数据库连接池组件,还有 SQL Parser 等组件,被大量业务和技术产品使用或集成,从 GitHub 的 Start 数量(25.5k),和 Used by 数量(206k)可见一斑。

github

网上关于 Druid 的文档,大多是数据库连接池组件的。SQL Parser 组件的文档,目前以官网 wiki 中的内容为主。

SQL Parser

SQL Parser 文档对此组件进行了简单清晰的介绍,重点内容如下:

Druid的 sql parser 是目前支持各种数据语法最完备的 SQL Parser。目前对各种数据库的支持如下:

数据库 DML DDL odps 完全支持 完全支持 mysql 完全支持 完全支持 postgresql 完全支持 完全支持 oracle 支持大部分 支持大部分 sql server 支持常用的 支持常用的ddl db2 支持常用的 支持常用的ddl hive 支持常用的 支持常用的ddl

druid 还缺省支持 sql-92 标准的语法,所以也部分支持其他数据库的 sql 语法。

Druid SQL Parser 分三个模块:

Parser:parser 是将输入文本转换为 ast(抽象语法树),parser 有包括两个部分,Parser 和Lexer,其中Lexer实现词法分析,Parser实现语法分析。 AST:AST是Abstract Syntax Tree的缩写,也就是抽象语法树。AST是parser输出的结果。 Visitor:Visitor是遍历AST的手段,是处理AST最方便的模式,Visitor是一个接口,有缺省什么都没做的实现VistorAdapter。

Druid SQL Parser 的使用场景

MySql SQL全量统计 Hive/ODPS SQL执行安全审计 分库分表SQL解析引擎 数据库引擎的SQL Parser

还有一些更具体的场景,比如:

SQL 格式化 SQL 添加条件 SQL 移除条件 SQL 参数化 SQL 翻译 SQL 翻译

SQL-92、SQL-99 等都是标准 SQL,mysql/oracle/pg/sqlserver/odps 等都是方言,也就是dialect。parser/ast/visitor 都需要针对不同的方言进行特别处理。—— 方言

SQL 翻译,即将一种方言,翻译成另一种。比如输入 MySQL 的 SQL 脚本,使用 MySQL 的 Parser 进行解析,再使用 Oracle 的 Visitor 进行遍历输出,就可以完成 MySQL 脚本到 Oracle 脚本的翻译:

List sqlStatements = SQLUtils.parseStatements(mysqlSql, DbType.mysql); String oracleSql = SQLUtils.toSQLString(sqlStatements, DbType.oracle); System.out.println(oracleSql);

然而在执行类似上面的代码片段进行 SQL 翻译时,你可能会遇到类似下面的报错:

java.lang.IllegalArgumentException: not support visitor type : com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor at com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlStatementImpl.accept0(MySqlStatementImpl.java:37) at com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlHintStatement.accept0(MySqlHintStatement.java:42) at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:49) at com.alibaba.druid.sql.SQLUtils.toSQLString(SQLUtils.java:436) at com.alibaba.druid.sql.SQLUtils.toSQLString(SQLUtils.java:364) at com.alibaba.druid.sql.SQLUtils.toSQLString(SQLUtils.java:356)

这种时候,想完成翻译的动作,就需要付出一些努力了。下面通过一个实例,来看一下如何将 MySQL 的脚本翻译成 H2 可用的脚本。

MySQL 脚本翻译成 H2 脚本

在当前最新的 1.2.8 Release 版本中,H2OutputVisitor.java 针对 H2 的方言处理并不多,直接进行翻译时,大概率会遇到类似上面的报错,或翻译出来的结果无法在 H2 中执行。

此时需要参照官方文档 实现自己的Visitor(或如下示例),在 Visitor 中针对 H2 方言进行处理,如:

public class CustomH2OutputVisitor extends H2OutputVisitor { public CustomH2OutputVisitor(Appendable appender) { super(appender); } public CustomH2OutputVisitor(Appendable appender, DbType dbType) { super(appender, dbType); } public CustomH2OutputVisitor(Appendable appender, boolean parameterized) { super(appender, parameterized); } public boolean visit(SQLCreateDatabaseStatement x) { /* https://h2database.com/html/commands.html#create_schema CREATE SCHEMA [ IF NOT EXISTS ] { name [ AUTHORIZATION ownerName ] | [ AUTHORIZATION ownerName ] } [ WITH tableEngineParamName [,...] ] */ printUcase("CREATE SCHEMA "); if (x.isIfNotExists()) { printUcase("IF NOT EXISTS "); } x.getName().accept(this); return false; } }

可参照下方示例,或 文档 使用自己实现的 Visitor:

String sql = "CREATE SCHEMA hinex;CREATE TABLE hinex.employees (jobTitle VARCHAR2(50));CREATE FULLTEXT INDEX hinex.jobTitle USING BTREE ON hinex.employees(jobTitle);"; List stmtList = SQLUtils.parseStatements(sql, DbType.mysql); StringBuilder out = new StringBuilder(); SQLASTOutputVisitor visitor = new CustomH2OutputVisitor(out); for (SQLStatement stmt : stmtList) { stmt.accept(visitor); visitor.println(); } System.out.println(out);

运行上面代码,会得到如下结果:

CREATE SCHEMA hinex; CREATE TABLE hinex.employees ( jobTitle VARCHAR2(50) ); CREATE INDEX hinex.jobTitle ON hinex.employees (jobTitle);

然而在逐步提升方言的翻译能力时,你会发现有些问题无法仅通过扩展 Visitor 来实现,比如上面提到的 java.lang.IllegalArgumentException: not support visitor type : com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor 这个报错,是由 MySqlStatementImpl.java:37 抛出的:

@Override protected void accept0(SQLASTVisitor visitor) { if (visitor instanceof MySqlASTVisitor) { accept0((MySqlASTVisitor) visitor); } else { throw new IllegalArgumentException("not support visitor type : " + visitor.getClass().getName()); } }

在这个 MySQL 的 Statement 中,仅支持通过 MySqlASTVisitor 进行遍历,遇到其他类型的 Visitor 都会抛出异常。如果我们是想将 MySQL 方言翻译成其他的方言,就需要修改这个类使其支持其他类型的 Visitor。

这类修改就不仅仅是扩展 Druid 了,而是要覆盖它的默认行为。这种覆盖类的方式,在 Override same class 中有所讨论:

如果我们能够将定制的类以 class 的形式放到发布包中,并且发布包是依赖 Servlet 容器运行的,依照 Servlet 规范中的要求及推荐,以及 Tomcat 的具体实现,能够实现覆盖; 但如果我们的目标是提供一个基础类库,供其他项目依赖,即定制的类也是在 JAR 包里时,就不好办了,因为原版 Druid JAR 包中,和我们基础类库的 JAR 包中,会存在相同的类。

怎么办?提供两种方法。

Pull Request

可以将扩展内容直接通过 Pull Request 提交给 Druid,如 #4777 和 #4778,待代码合并进主干,并且 Druid 发布包含这些 PR 内容的新版本后,就可以直接使用了。

maven-assembly-plugin

当然,上面那种方法的流程可能会比较长,如果等不及,可以在定制的类库中,使用 maven-assembly-plugin 来将所有的依赖发布成一个定制版的 Druid JAR 包,在使用时用定制版替代原版即可。

在 pom.xml 中添加类似下面的内容:

org.apache.maven.plugins maven-assembly-plugin false jar-with-dependencies make-assembly package single

这样在执行 mvn package 打包的时候,就会构建出一个包含了所有依赖的版本。通过 install 或 deploy 等命令将定制版 Druid 发布到 Maven 仓库中,就可以被其他项目所使用了。

注意:仅发布包含依赖版本时,需设置上面的 appendAssemblyId 属性为 false,否则会发布包含依赖和不包含依赖两个版本。

在 MySQL 脚本转 H2 中介绍过一个 mysql2h2-converter 项目,其 v0.2.2 版本是使用 JavaCC 编写的 MySQL Parser。v0.3.0 版本便是基于 Druid 1.2.8 进行的扩展,并提供了可以独立使用的 命令行工具 和可被项目依赖的 定制版本,在需要进行 MySQL 脚本转 H2 脚本操作的时候,可以直接使用。

这里是其中最主要的 H2OutputVisitor.java 和 单元测试 。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有